package top.dreamix.njitkit.service

import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.DEFAULT
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.forms.FormDataContent
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.Parameters
import io.ktor.http.URLBuilder
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.util.io.pem.PemObject
import org.bouncycastle.util.io.pem.PemReader
import top.dreamix.ktor.plugin.SaveSetCookie
import top.dreamix.ktor.plugin.SavedCookies
import java.io.StringReader
import java.security.KeyFactory
import java.security.PublicKey
import java.security.Security
import java.security.spec.X509EncodedKeySpec
import java.util.Base64
import java.util.regex.Pattern
import javax.crypto.Cipher
import kotlin.random.Random

object Yiban {
    private val API_BASE_URL = "https://m.yiban.cn"
    private val httpClient: HttpClient by lazy {
        HttpClient(CIO) {
            // 安装 JSON 序列化支持
            install(ContentNegotiation) {
                json(Json {
                    ignoreUnknownKeys = true
                    isLenient = true
                    prettyPrint = true
                })
            }

            // 安装日志记录（可选，用于调试）
            install(Logging) {
                logger = Logger.DEFAULT
                level = LogLevel.HEADERS
            }

            // 配置引擎参数
            engine {
                // 最大请求数
                maxConnectionsCount = 1000
                // 每个端点的最大请求数
                endpoint.maxConnectionsPerRoute = 100
                // 请求超时设置
                requestTimeout = 30000
            }

            // 配置默认请求头
            defaultRequest {
//                header("Accept", "*/*")
//                header("accept-encoding", "gzip, deflate, br, zstd")
//                header("cache-control", "no-cache")
//                header("host", "www.yiban.cn")
//                header("origin", "https://www.yiban.cn")
//                header("x-requested-with", "XMLHttpRequest")
//                header("sec-ch-ua-mobile", "?0")
//                header("sec-ch-ua-platform", "\"Windows\"")
//                header("sec-fetch-dest", "empty")
//                header("sec-fetch-mode", "cors")
//                header("sec-fetch-site", "same-origin")
//                header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
//                header("Connection", "keep-alive")
//                header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0")
                header("VersionCode", "3011")
                header("AppVersion", "5.1.2")
                header("User-Agent", "YiBan/5.1.2(3011) Mozilla/5.0 (Linux; Android 13; MI 9 Build/TKQ1.221114.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/128.0.6613.146 Mobile Safari/537.36")
            }
        }
    }
    suspend fun configureAccount(access_token: String, callback: (Schema.LoginResp) -> Unit) {
        val url = URLBuilder("$API_BASE_URL/api/v4/passport/Login").build()
        val resp: Schema.LoginResp = httpClient.get(url) {
            header("Authorization", "Bearer $access_token")
            header("loginToken", access_token)
        }.body()
        callback(resp)
    }

    suspend fun login(mobile: String, password: String, rid: String?): Schema.LoginResp {
        val encryptedPassword = Util.encryptRsa(password)
        val url = URLBuilder("$API_BASE_URL/api/v4/passport/login").build()
        val resp: Schema.LoginResp = httpClient.post(url) {
            rid?.let { header("ybticket", "{\"rid\":\"$rid\",\"vendor\":\"shumei\"}") }
            contentType(ContentType.Application.Json)
            setBody(Schema.YibanLoginReq(mobile, encryptedPassword, "1", "1"))
        }.body()
        return resp
    }

    private val json = Json { ignoreUnknownKeys = true }

    object SchoolBased {
        private val client: HttpClient by lazy {
            HttpClient(CIO) {

                // 安装 JSON 序列化支持
                install(ContentNegotiation) {
                    json(Json {
                        ignoreUnknownKeys = true
                        isLenient = true
                        prettyPrint = true
                    })
                }

//                install(ContentEncoding) {
//                    gzip()
//                    deflate()
//                }

                // 安装日志记录（可选，用于调试）
                install(Logging) {
                    logger = Logger.DEFAULT
                    level = LogLevel.HEADERS
                }

                install(SaveSetCookie)

                // 配置引擎参数
                engine {
                    // 最大请求数
                    maxConnectionsCount = 1000
                    // 每个端点的最大请求数
                    endpoint.maxConnectionsPerRoute = 100
                    // 请求超时设置
                    requestTimeout = 30000
                }

                // 配置默认请求头
                defaultRequest {
                    header("sec-ch-ua", "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Android WebView\";v=\"128\"")
                    header("sec-ch-ua-mobile", "?1")
                    header("sec-ch-ua-platform", "\"Android\"")
                    header("Upgrade-Insecure-Requests", "1")
                    // User-Agent 带有Android 好像会提示请在授权设备签到
//                    header("User-Agent", "Mozilla/5.0 (Linux; Android 13; MI 9 Build/TKQ1.221114.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/128.0.6613.146 Mobile Safari/537.36;webank/h5face;webank/2.0 yiban_android/5.1.2")
                    header("User-Agent", "Yiban")
                    header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
                    header("appversion", "5.1.2")
                    header("X-Requested-With", "com.yiban.app")
                    header("Sec-Fetch-Site", "none")
                    header("Sec-Fetch-Mode", "navigate")
                    header("Sec-Fetch-User", "?1")
                    header("Sec-Fetch-Dest", "document")
//                    header("Accept-Encoding", "gzip, deflate, br, zstd")
                    header("Accept-Language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7")
                }
                followRedirects = false
            }
        }

        private var userInfo: Schema.IAppUserInfo? = null

        suspend fun auth(accessToken: String): String {
            // 第一步：获取验证请求
            val response = client.get("https://f.yiban.cn/iapp/index") {
                parameter("act", "iapp7463")
                header("authorization", "Bearer $accessToken")
                header("logintoken", accessToken)
            }

            val location = response.headers["Location"]
                ?: throw Exception("校本化认证失败: ${response.bodyAsText()}")
//
            val verify = extractVerifyRequest(location)

//            // 第二步：重新认证
//            reAuth(verify)

//            // 第三步：完成认证
            var authResponseText = client.get("https://api.uyiban.com/base/c/auth/yiban") {
                parameter("verifyRequest", verify)
                header("Origin", "https://c.uyiban.com")
                header("referer", "https://c.uyiban.com/")
                header("cookie", "csrf_token=${getCSRF()}")
                parameter("CSRF", getCSRF())
            }.bodyAsText()

//            val hex = authResponseText.readRawBytes().joinToString("") { "%02x".format(it) }
//            println(hex) // 打印前100字节的十六进制

            var authResponse: Schema.IAppAuthResponse

            try {
                authResponse = json.decodeFromString(authResponseText)
            } catch (e: Exception) {
                yibanOAuth(accessToken)
                authResponseText = client.get("https://api.uyiban.com/base/c/auth/yiban") {
                    parameter("verifyRequest", verify)
                    header("Origin", "https://c.uyiban.com")
                    header("referer", "https://c.uyiban.com/")
                    header("cookie", "csrf_token=${getCSRF()}")
                    parameter("CSRF", getCSRF())
                }.bodyAsText()
                authResponse = json.decodeFromString(authResponseText)
            }

            if (authResponse.code == 0) {
                userInfo = authResponse.data
                // 返回当前会话的cookies
                return getAllCookies()
            } else {
                throw Exception("Auth Error ${authResponse.msg}")
            }
        }

        private suspend fun yibanOAuth(accessToken: String) {
            val clientId = "95626fa3080300ea"
            val redirectUri = "https://f.yiban.cn/iapp7463"

            val oauthResponse = client.get("https://oauth.yiban.cn/code/html") {
                parameter("client_id", clientId)
                parameter("redirect_uri", redirectUri)
            }

            val htmlContent = oauthResponse.bodyAsText()
            val pageUseValue = extractPageUseValue(htmlContent)

            client.post("https://oauth.yiban.cn/code/usersure") {
                parameter("ajax_sign", pageUseValue)
                header("Cookie", "loginToken=$accessToken;${getAllCookies()}")
                header("Origin", "https://oauth.yiban.cn")
                header("Referer", "https://oauth.yiban.cn/code/html?client_id=$clientId&redirect_uri=$redirectUri")
                setBody(FormDataContent(Parameters.build {
                    append("client_id", clientId)
                    append("redirect_uri", redirectUri)
                    append("oauth_upwd", "false")
                    append("state", "")
                    append("scope", "1,2,3,4,100,")
                    append("display", "authorize")
                }))
            }
        }

        private suspend fun reAuth(verify: String) {
            val clientId = "95626fa3080300ea"
            val redirectUri = "https://f.yiban.cn/iapp7463"

            client.get("https://api.uyiban.com/base/c/auth/yiban") {
                parameter("verifyRequest", verify)
                parameter("CSRF", getCSRF())
            }

            val oauthResponse = client.get("https://oauth.yiban.cn/code/html") {
                parameter("client_id", clientId)
                parameter("redirect_uri", redirectUri)
            }

            val htmlContent = oauthResponse.bodyAsText()
            val pageUseValue = extractPageUseValue(htmlContent)

            client.post("https://oauth.yiban.cn/code/usersure") {
                parameter("ajax_sign", pageUseValue)
                setBody(mapOf(
                    "client_id" to clientId,
                    "redirect_uri" to redirectUri,
                    "oauth_upwd" to "false"
                ))
            }
        }

        private fun extractVerifyRequest(location: String): String {
            val regex = Regex("verify_request=(.*?)&")
            val match = regex.find(location)
            return match?.groupValues?.get(1) ?: throw Exception("无法提取verify_request")
        }

        private fun extractPageUseValue(htmlContent: String): String {
            val pattern = Pattern.compile("var page_use\\s*=\\s*'([^']+)'")
            val matcher = pattern.matcher(htmlContent)
            return if (matcher.find()) {
                matcher.group(1)
            } else {
                ""
            }
        }

        private fun getCSRF(): String {
            // 这里需要实现获取CSRF的方法，根据实际业务逻辑
            // 暂时返回空字符串，需要根据实际情况实现
            return "00000"
        }

        suspend fun getAllCookies(): String {
            val cookiesList = SavedCookies.yiban
            var cookie = ""
            cookiesList.forEach {
                cookie += "${it.key}=${it.value};"
            }
            return cookie
        }
        suspend fun getNightAttendanceTask(cookie: String): Schema.NightAttendanceTaskResp {
            val url = URLBuilder("https://api.uyiban.com/nightAttendance/student/index/signPosition").build()
            val text = client.get(url) {
                header("Origin", "https://c.uyiban.com")
                header("Cookie", cookie+"csrf_token=${getCSRF()}")
                parameter("CSRF", getCSRF())
            }.bodyAsText()
            return json.decodeFromString(text)
        }

        suspend fun nightAttendanceSignIn(cookie: String, task: Schema.NightAttendanceTaskData): Schema.NightAttendanceTaskSignInResp {
            val position = task.Position.random()
            val points = Util.parseCoordinates(position.Points)
            val selectedPoint = Util.generateRandomPointInPolygon(points)
            val data = "{\"Reason\":\"\",\"AttachmentFileName\":\"\",\"LngLat\":\"${selectedPoint.x},${selectedPoint.y}\",\"Address\":\"${position.Address}\"}"
            val url = URLBuilder("https://api.uyiban.com/nightAttendance/student/index/signIn").build()
            val text = client.post(url) {
                parameter("CSRF", getCSRF())
                header("Cookie", cookie+"csrf_token=${getCSRF()}")
                header("Origin", "https://c.uyiban.com",)
                contentType(ContentType.Application.FormUrlEncoded)
                setBody(FormDataContent(Parameters.build {
                    append("Code", "")
                    append("PhoneModel", "")
                    append("SignInfo", data)
                    append("OutState", "1")
                }))
            }.bodyAsText()
            return json.decodeFromString(text)
        }
    }

    object Util {

        private val PUBLIC_KEY = """
        -----BEGIN PUBLIC KEY-----
        MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzq0rgsM++ZxLRGHpdfre
        Hu6UXhdlUS5P2WOxRG14qU8/iWSb/CkOqgOl8AGcOhlthkvolCdpUvVcVsVUxBv0
        YRN0Jb64zPrn5aLVwQT4RJn5tXvoqLdHIXis7pljXAMDPVZOVlWJkDMk8YU6HDaA
        MqsD6l5p9lg2LMP4OhMgaPX+CkO370LB5vRjJTHp03n+IqfxXoC7DEd+kxRIEM2C
        EDgUSYDJBDgwBvGALZmvB/a1b0im9t1P/EmnuE7uN9NRFoWyVpOiEwo/Ti7rmJGf
        qNT3vvtfWo4nXsm1rYQXsPayoKDSRaba3gFY/1SYWLAuSO2q2da5ZCcsAk5RKy0V
        c1hUg8n6y0YLAvuzoXY5VyNMXkhH5Zc5Kg64b5RxILeZpZG0MV7GFY3sw//k7SNg
        darKT8A0Iv3l3lfguX3HNi6dkf97kS/EiA0tbkIB/JNjv13mq8HL7LijRt2hkKqP
        PhQW88xC/exZilU5pAavoZOPuZIOTUHqtpRq4ZeKl+wDf+e5lPYFDpihWGjplGpa
        4BOSmGeo/SyVFPji9QF4Pk0DRJF/NjwJoAC60xHAVt5Z4gQSOOOjNZDCswA0ry2L
        e8m5cv5vPGY75uVrGqALQ6Xm961PPc5cJ1q7tmEZMj+z5HE7tgAdhiPI6acKgrAv
        +1k4N0OVqKamMS+PVpD05hUCAwEAAQ==
        -----END PUBLIC KEY-----
        """.trimIndent()

        init {
            // 注册 Bouncy Castle 提供者
            if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
                Security.addProvider(BouncyCastleProvider())
            }
        }

        fun encryptRsa(data: String): String {
            val publicKey = loadPublicKey(PUBLIC_KEY)
            val cipher = Cipher.getInstance("RSA/None/PKCS1Padding", BouncyCastleProvider.PROVIDER_NAME)
            cipher.init(Cipher.ENCRYPT_MODE, publicKey)
            val encryptedBytes = cipher.doFinal(data.toByteArray(Charsets.UTF_8))
            return Base64.getEncoder().encodeToString(encryptedBytes)
        }

        private fun loadPublicKey(pemString: String): PublicKey {
            val pemReader = PemReader(StringReader(pemString))
            val pemObject: PemObject = pemReader.readPemObject()
            pemReader.close()
            val keySpec = X509EncodedKeySpec(pemObject.content)
            val keyFactory = KeyFactory.getInstance("RSA")
            return keyFactory.generatePublic(keySpec)
        }

        data class Point(val x: Double, val y: Double)

        fun parseCoordinate(coordStr: String): Point {
            val parts = coordStr.trim().split(",")
            require(parts.size == 2) { "坐标格式错误: $coordStr" }
            val x = parts[0].toDoubleOrNull()
            val y = parts[1].toDoubleOrNull()
            require(x != null && y != null) { "坐标值无效: $coordStr" }
            return Point(x, y)
        }

        fun parseCoordinates(coordStrings: List<String>): List<Point> {
            return coordStrings.map { parseCoordinate(it) }
        }

        fun isPointInPolygon(point: Point, polygon: List<Point>): Boolean {
            var inside = false
            val n = polygon.size
            var j = n - 1

            for (i in 0 until n) {
                val xi = polygon[i].x
                val yi = polygon[i].y
                val xj = polygon[j].x
                val yj = polygon[j].y

                val intersect = ((yi > point.y) != (yj > point.y)) &&
                        (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi)
                if (intersect) inside = !inside
                j = i
            }
            return inside
        }

        fun generateRandomPointInPolygon(polygon: List<Point>): Point {
            require(polygon.size >= 3) { "多边形至少需要3个点" }

            // 计算包围盒
            var minX = polygon[0].x
            var maxX = polygon[0].x
            var minY = polygon[0].y
            var maxY = polygon[0].y

            for (point in polygon) {
                if (point.x < minX) minX = point.x
                if (point.x > maxX) maxX = point.x
                if (point.y < minY) minY = point.y
                if (point.y > maxY) maxY = point.y
            }

            // 随机生成点直到找到一个在多边形内的点
            while (true) {
                val randomX = Random.nextDouble(minX, maxX)
                val randomY = Random.nextDouble(minY, maxY)
                val randomPoint = Point(randomX, randomY)

                if (isPointInPolygon(randomPoint, polygon)) {
                    return randomPoint
                }
            }
        }

    }
    class Schema {
        @Serializable
        data class NightAttendanceTaskSignInResp (
            val code: Int,
            val msg: String,
            val data: Boolean? = null
        )
        @Serializable
        data class NightAttendanceTaskResp(
            val code: Int,
            val msg: String,
            val data: NightAttendanceTaskData?
        )

        @Serializable
        data class NightAttendanceTaskData(
            val State: Int,
            val Msg: String,
            val OutState: String? = "",
            val Remark: String? = "",
            val FileUrl: String? = "",
            val Type: String? = "",
            val Position: List<NightAttendancePositionInfo> = emptyList(),
            val Range: NightAttendanceTimeRange? = null,
            val IsNeedPhoto: Int,
            val AttachmentFileName: String? = ""
        )

        @Serializable
        data class NightAttendancePositionInfo(
            val Id: String,
            val UniversityId: String,
            val Campus: String,
            val BuildingId: Long? = null,
            val Type: String,
            val Title: String,
            val Address: String,
            val LngLat: String,
            val Range: Int,
            val MapType: Int,
            val Points: List<String> = emptyList(),
            val AddressName: String,
            val CreateTime: Long? = null
        )

        @Serializable
        data class NightAttendanceTimeRange(
            val StartTime: Long,
            val EndTime: Long,
            val SignDay: Int,
            val RelatType: Int? = null,
            val RelatTimeType: Int? = null
        )
        /**
         * API 响应基础结构
         * @property response 响应状态码（100 表示请求成功）
         * @property message 响应消息
         * @property is_mock 是否为模拟数据
         * @property data 响应数据主体
         */
        @Serializable
        data class LoginResp(
            val response: Int,
            val message: String,
            val data: JsonElement? = null
        )

        /**
         * 用户数据主体
         * @property user 用户详细信息
         * @property access_token 用户访问令牌
         * @property urlBlackListLastUpdateTime URL 黑名单最后更新时间（Unix 时间戳）
         */
        @Serializable
        data class LoginUserData(
            val user: LoginUser,
            val access_token: String,
            val urlBlackListLastUpdateTime: Long
        )

        /**
         * 验证码载体
         * @property vendor 用户详细信息
         * @property mode 验证码模式
         */
        @Serializable
        data class CaptchaData(
            val vendor: String,
            val mode: String,
        )

        /**
         * 用户详细信息
         * @property sex 性别（"0" 表示未知/未设置，"1" 表示男，"2" 表示女）
         * @property name 真实姓名
         * @property nick 用户昵称
         * @property pic 用户头像（不同尺寸）
         * @property user_id 用户ID
         * @property phone 手机号（与 mobile 相同）
//         * @property mobile 手机号
         * @property authority 权限等级
         * @property isSchoolVerify 是否通过学校验证
         * @property school 学校信息
         */
        @Serializable
        data class LoginUser(
            val sex: String,
            val name: String,
            val nick: String,
            val pic: Avatar,
            val user_id: Long,
            val phone: String,
//            val mobile: String,
            val authority: String,
            val isSchoolVerify: Boolean,
            val school: SchoolInfo
        )

        /**
         * 用户头像（不同尺寸）
         * @property s 小尺寸头像（68x68）
         * @property m 中尺寸头像（88x88）
         * @property b 大尺寸头像（160x160）
         * @property o 原始尺寸头像
         */
        @Serializable
        data class Avatar(
            val s: String,
            val m: String,
            val b: String,
            val o: String
        )

        /**
         * 学校认证信息
         * @property isVerified 是否已验证
         * @property schoolName 学校名称
         * @property schoolId 学校ID
         * @property schoolOrgId 学校组织ID
         * @property collegeName 学院名称
         * @property collegeId 学院ID
         * @property className 班级名称
         * @property classNameSerial 班级全称（包含专业）
         * @property classId 班级ID
         * @property joinSchoolYear 入学年份
         * @property type 类型（1 表示本科生）
         */
        @Serializable
        data class SchoolInfo(
            val isVerified: Boolean,
            val schoolName: String,
            val schoolId: Int,
            val schoolOrgId: Int,
            val collegeName: String,
            val collegeId: Int,
            val className: String,
            val classNameSerial: String,
            val classId: Int,
            val joinSchoolYear: String,
            val type: Int
        )

        @Serializable
        data class IAppAuthResponse(
            val code: Int,
            val msg: String,
            val data: IAppUserInfo? = null
        )
        @Serializable
        data class IAppUserInfo(
            val UniversityName: String,
            val UniversityId: String,
            val PersonId: String,
            val PersonName: String,
            val State: Int,
            val PersonType: String,
            val UniversityIcon: String,
            val Container: String,
            val HomeTheme: String,
//            val CustomApps: IAppCustomApps,
//            val Group: List<Any>,
//            val Apps: List<IApp>,
            val WxState: Int,
            val DingDingState: Int
        )
//        @Serializable
//        data class IAppCustomApps(
//            val up: List<Any>,
//            val down: List<Any>
//        )
//        @Serializable
//        data class IApp(
//            val Id: String,
//            val ServiceId: String,
//            val AppIcon: String,
//            val AppUrl: String,
//            val AppRuleUrl: String,
//            val AuthCode: String,
//            val AppName: String
//        )
        @Serializable
        data class YibanLoginReq(
            val mobile: String,
            val password: String,
            val ct: String,
            val identify: String,
        )
    }
}